﻿using System;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;

namespace Itau.IM.Common.Mainframe.Book
{
    public class BookSerializer
    {
        public static object Deserialize(string data, Type type)
        {
            if (data.Contains("ABEND"))
            {
                throw new Exception(string.Format("Erro na execução do serviço: {0}", data));
            }
            object obj = Activator.CreateInstance(type);
            foreach (PropertyInfo prop in type.GetProperties())
            {
                foreach (Attribute atbt in prop.GetCustomAttributes(true))
                {
                    if (typeof(BookFieldBaseAttribute).IsAssignableFrom(atbt.GetType()))
                    { 
                        int StartIndex = ((BookFieldBaseAttribute)atbt).StartIndex - 1;
                        int Length = GetObjectLength((BookFieldBaseAttribute)atbt,prop.PropertyType, obj);

                        prop.SetValue(obj, GetObjectFromString(data.Substring(StartIndex, Length), (BookFieldBaseAttribute)atbt, prop.PropertyType, obj), null);
                    }
                }
            }
            return obj;
        }

        private static int GetObjectLength(BookFieldBaseAttribute atbt, Type objectType, object obj)
        {
            int Length = 0;

            if (atbt.GetType() == typeof(BookBooleanFieldAttribute))
            {
                Length = ((BookBooleanFieldAttribute)atbt).TrueValue.Length;
            }
            else if (atbt.GetType() == typeof(BookCollectionFieldAttribute))
            {
                Length = ((BookCollectionFieldAttribute)atbt).Length * ((BookCollectionFieldAttribute)atbt).DefaultItemValue.Length;
                if (((BookCollectionFieldAttribute)atbt).InsertListCount)
                {
                    Length += ((BookCollectionFieldAttribute)atbt).ListCountFormat.Length;
                }
                else if (!string.IsNullOrEmpty(((BookCollectionFieldAttribute)atbt).MaxItensProperty)
                    && ((BookCollectionFieldAttribute)atbt).MaxItensType != null)
                {
                    PropertyInfo propertyMaxItens = ((BookCollectionFieldAttribute)atbt).MaxItensType.GetProperty(((BookCollectionFieldAttribute)atbt).MaxItensProperty);
                    object valorMaxItens = propertyMaxItens.GetValue(obj, null);
                    int maxItems = Convert.ToInt32(valorMaxItens);
                    Length = maxItems * ((BookCollectionFieldAttribute)atbt).DefaultItemValue.Length;
                }
            }
            else if (atbt.GetType() == typeof(BookDateFieldAttribute))
            {
                Length = ((BookDateFieldAttribute)atbt).DateTimeFormat.Length;
            }
            else if (atbt.GetType() == typeof(BookNumberFieldAttribute))
            {
                Length = ((BookNumberFieldAttribute)atbt).IntegerDigits + ((BookNumberFieldAttribute)atbt).DecimalDigits;
            }
            else if (atbt.GetType() == typeof(BookStringFieldAttribute)) 
            {
                Length = ((BookStringFieldAttribute)atbt).Length;
            }
            else if (atbt.GetType() == typeof(BookObjectFieldAttribute))
            {
                foreach (Attribute innerAtbt in objectType.GetCustomAttributes(true))
                {
                    if (typeof(BookClassAttribute).IsAssignableFrom(innerAtbt.GetType()))
                    {
                        Length = ((BookClassAttribute)innerAtbt).Length;
                    }
                }
            }
            return Length;
        }

        private static object GetObjectFromString(string data, BookFieldBaseAttribute atbt, Type targetType, object obj)
        {
            object valueConverted = null;

            if (atbt.GetType() == typeof(BookNumberFieldAttribute))
            {
                if (!string.IsNullOrEmpty(data) && data.Trim().Length > 0)
                {
                    Double number = Double.Parse(data) / Math.Pow(10, ((BookNumberFieldAttribute)atbt).DecimalDigits);

                    if (targetType == typeof(Int32))
                        valueConverted = Convert.ToInt32(Math.Truncate(number));
                    else if (targetType == typeof(Single))
                        valueConverted = Convert.ToSingle(number);
                    else if (targetType == typeof(Double))
                        valueConverted = (Double)number;
                    else if (targetType == typeof(Int64))
                        valueConverted = Convert.ToInt64(Math.Truncate(number));
                    else if (targetType == typeof(Int16))
                        valueConverted = Convert.ToInt16(Math.Truncate(number));
                    else if (targetType == typeof(Decimal))
                        valueConverted = Convert.ToDecimal(number);
                    else if (targetType == typeof(Byte))
                        valueConverted = Convert.ToByte(Math.Truncate(number));
                    else if (targetType == typeof(UInt32))
                        valueConverted = Convert.ToUInt32(Math.Truncate(number));
                    else if (targetType == typeof(UInt64))
                        valueConverted = Convert.ToUInt64(Math.Truncate(number));
                    else if (targetType == typeof(UInt16))
                        valueConverted = Convert.ToUInt16(Math.Truncate(number));
                    else
                        throw new ArgumentOutOfRangeException(string.Format("A propriedade é de um tipo não suportado pelo {0}", atbt.GetType().Name));
                }
            }
            else if (atbt.GetType() == typeof(BookBooleanFieldAttribute))
            {
                if (data == ((BookBooleanFieldAttribute)atbt).TrueValue)
                {
                    valueConverted = true;
                }
                else if (data == ((BookBooleanFieldAttribute)atbt).FalseValue)
                {
                    valueConverted = false;
                }
                else
                {
                    throw new Exception(string.Format("{0}: A propriedade recebeu um valor inválido.", atbt.GetType().Name));
                }
            }
            else if(atbt.GetType() == typeof(BookDateFieldAttribute))
            {
                if (!string.IsNullOrEmpty(data) && data.Trim().Length > 0)
                {
                    valueConverted = DateTime.ParseExact(data, ((BookDateFieldAttribute)atbt).DateTimeFormat, ((BookDateFieldAttribute)atbt).Culture.DateTimeFormat);
                    if (targetType == typeof(TimeSpan))
                    { 
                        valueConverted = new TimeSpan(((DateTime)valueConverted).Ticks);
                    }
                }
            }
            else if (atbt.GetType() == typeof(BookStringFieldAttribute))
            {
                if (!string.IsNullOrEmpty(data))
                {
                    if (targetType == typeof(char))
                    {
                        valueConverted = Convert.ToChar(data);
                    }
                    else
                    {
                        valueConverted = data.Trim();
                    }
                }
            }
            else if (atbt.GetType() == typeof(BookObjectFieldAttribute))
            {
                if (!string.IsNullOrEmpty(data) && data.Trim().Length > 0)
                {
                    valueConverted = Deserialize(data, targetType);
                }
            }
            else if (atbt.GetType() == typeof(BookCollectionFieldAttribute))
            {
                int maxItems = ((BookCollectionFieldAttribute)atbt).Length;
                if (((BookCollectionFieldAttribute)atbt).InsertListCount)
                {
                    maxItems = Convert.ToInt32(data.Substring(0, ((BookCollectionFieldAttribute)atbt).ListCountFormat.Length));
                    data = data.Remove(0, ((BookCollectionFieldAttribute)atbt).ListCountFormat.Length);
                }
                else if (!string.IsNullOrEmpty(((BookCollectionFieldAttribute)atbt).MaxItensProperty)
                    && ((BookCollectionFieldAttribute)atbt).MaxItensType != null)
                {
                    PropertyInfo propertyMaxItens = ((BookCollectionFieldAttribute)atbt).MaxItensType.GetProperty(((BookCollectionFieldAttribute)atbt).MaxItensProperty);
                    object valorMaxItens = propertyMaxItens.GetValue(obj, null);
                    maxItems = Convert.ToInt32(valorMaxItens);
                }

                if (!targetType.IsInterface)
                {
                    valueConverted = Activator.CreateInstance(targetType);
                }
                else
                {

                    Type listType = Type.GetType(string.Format("System.Collections.Generic.List`1[[{0}]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", targetType.GetGenericArguments()[0].AssemblyQualifiedName, true));
                    valueConverted = Activator.CreateInstance(listType);
                }
                IList list = (IList)valueConverted;
                StringBuilder sbCollection = new StringBuilder(data);
                while (sbCollection.Length > 0 && maxItems > 0)
                {
                    list.Add(GetObjectFromString(sbCollection.ToString().Substring(0, ((BookCollectionFieldAttribute)atbt).DefaultItemValue.Length), ((BookCollectionFieldAttribute)atbt).ItemAttribute, ((BookCollectionFieldAttribute)atbt).ItemType, obj));
                    sbCollection = sbCollection.Remove(0, ((BookCollectionFieldAttribute)atbt).DefaultItemValue.Length);
                    maxItems--;
                }
            }   

            return valueConverted;
        }

        public static string Serialize(object obj)
        {
            object[] classAttributes = obj.GetType().GetCustomAttributes(typeof(BookClassAttribute), true);
            BookClassAttribute classAttribute = null;

            if (classAttributes.Length == 0)
            {
                throw new Exception(string.Format("Atributo BookClassAttribute não encontrado na classe {0}", obj.GetType().Name));
            }
            else
            {
                classAttribute = (BookClassAttribute)classAttributes[0];
            }

            StringBuilder sb = new StringBuilder();
            Object[] attributes;
            SortedDictionary<int, string> StringsToMount = new SortedDictionary<int, string>();


            foreach (PropertyInfo prop in obj.GetType().GetProperties())
            { 
                attributes = prop.GetCustomAttributes(true);
                if (attributes != null && attributes.Length > 0)
                {
                    foreach (Attribute atbt in attributes)
                    { 
                        if(typeof(BookFieldBaseAttribute).IsAssignableFrom(atbt.GetType()))
                        {
                            if (atbt.GetType() != typeof(BookCollectionFieldAttribute))
                            {
                                EnqueueStringFromObject(prop.GetValue(obj, null), (BookFieldBaseAttribute)atbt, StringsToMount);
                            }
                            else
                            {
                                if (prop.GetValue(obj, null) == null)
                                {
                                    StringBuilder sbEmptyCollection = new StringBuilder();
                                    sbEmptyCollection.Append(((BookCollectionFieldAttribute)atbt).DefaultItemValue, 0, ((BookCollectionFieldAttribute)atbt).Length);
                                    StringsToMount.Add(((BookFieldBaseAttribute)atbt).StartIndex, sbEmptyCollection.ToString());
                                }
                                else if (prop.GetValue(obj, null).GetType().GetInterface("IList", false) == null)
                                {
                                    throw new Exception(string.Format("Propriedades com o atributo {0} precisam ser de tipos que implementem a interface IList", atbt.GetType().Name));
                                }
                                else
                                {
                                    SortedDictionary<int, string> listaValores = new SortedDictionary<int, string>();
                                    int counter = 0;
                                    IList propertyValue = (IList)prop.GetValue(obj, null);
                                    foreach (object item in propertyValue)
                                    {
                                        if (counter >= propertyValue.Count)
                                        {
                                            listaValores.Add(counter, ((BookCollectionFieldAttribute)atbt).DefaultItemValue);
                                        }
                                        else
                                        {
                                            ((BookCollectionFieldAttribute)atbt).ItemAttribute.StartIndex = counter;
                                            EnqueueStringFromObject(item, ((BookCollectionFieldAttribute)atbt).ItemAttribute, listaValores);
                                        }
                                        counter++;
                                    }
                                    StringBuilder sbList = new StringBuilder();
                                    foreach (string item in listaValores.Values)
                                    {
                                        sbList.Append(item);
                                    }

                                    if (StringsToMount.ContainsKey(((BookFieldBaseAttribute)atbt).StartIndex))
                                    {
                                        throw new Exception(string.Format("O tipo {0} possui mais de uma propriedade com StartIndex igual a {1}", obj.GetType().Name, ((BookFieldBaseAttribute)atbt).StartIndex));
                                    }

                                    if (((BookCollectionFieldAttribute)atbt).InsertListCount)
                                    {
                                        sbList.Insert(0, listaValores.Count.ToString(((BookCollectionFieldAttribute)atbt).ListCountFormat));
                                    }
                                    StringsToMount.Add(((BookFieldBaseAttribute)atbt).StartIndex, sbList.ToString());
                                }
                            }
                            
                        }
                    }
                    
                }
            }

            foreach (int key in StringsToMount.Keys)
            {
                if (sb.Length > key - 1)
                {
                    throw new Exception("Encontrada sobreposição de propriedades (O StartIndex de uma propriedade está apontando para um local na string onde já existe o valor de outra propriedade)");
                }
                else
                {
                    if (sb.Length < key - 1)
                    {
                        sb.Append(' ', (key - 1) - sb.Length);
                    }
                    sb.Append(StringsToMount[key]);
                }
            }

            if (classAttribute.Length > sb.Length)
            {
                sb.Append(' ', classAttribute.Length - sb.Length);
            }

            if (!string.IsNullOrEmpty(classAttribute.PrependString))
            {
                sb.Insert(0, classAttribute.PrependString);
            }

            return sb.ToString();
        }

        private static void EnqueueStringFromObject(object value, BookFieldBaseAttribute atbt, SortedDictionary<int, string> stringsToMount)
        {
            string Value = null;

            if (atbt.GetType() == typeof(BookObjectFieldAttribute))
            {
                if (value != null)
                {
                    BookObjectFieldAttribute boolAtbt = (BookObjectFieldAttribute)atbt;
                    Value = BookSerializer.Serialize(value);
                }
            }
            else if (atbt.GetType() == typeof(BookBooleanFieldAttribute))
            {
                if (value.GetType() != typeof(Boolean))
                {
                    throw new ArgumentOutOfRangeException(string.Format("A propriedade do tipo {0} não é suportada pelo {1}", value.GetType().Name, atbt.GetType().Name));
                }
                else
                {
                    Value = (bool)value ? ((BookBooleanFieldAttribute)atbt).TrueValue : ((BookBooleanFieldAttribute)atbt).FalseValue;
                }
            }
            else if (atbt.GetType() == typeof(BookDateFieldAttribute))
            {
                if (value.GetType() == typeof(DateTime))
                {
                    Value = ((DateTime)value).ToString(((BookDateFieldAttribute)atbt).DateTimeFormat);
                }
                else if (value.GetType() == typeof(TimeSpan))
                {
                    DateTime tempDate = new DateTime(((TimeSpan)value).Ticks);
                    Value = tempDate.ToString(((BookDateFieldAttribute)atbt).DateTimeFormat);
                }
                else
                {
                    throw new ArgumentOutOfRangeException(string.Format("A propriedade do tipo {0} não é suportada pelo {1}", value.GetType().Name, atbt.GetType().Name));
                }
            }
            else if (atbt.GetType() == typeof(BookStringFieldAttribute))
            {
                if (value == null)
                {
                    Value = String.Empty;
                }
                else
                {
                    Value = value.ToString();
                }

                Value = Value.PadRight(((BookStringFieldAttribute)atbt).Length, ((BookStringFieldAttribute)atbt).FillerChar);
                if (Value.Length > ((BookStringFieldAttribute)atbt).Length)
                {
                    Value = Value.Substring(0, ((BookStringFieldAttribute)atbt).Length);
                }
            }
            else if (atbt.GetType() == typeof(BookNumberFieldAttribute))
            {
                BookNumberFieldAttribute numberAtbt = (BookNumberFieldAttribute)atbt;
                string numberFormat = string.Empty.PadLeft(((BookNumberFieldAttribute)atbt).DecimalDigits + ((BookNumberFieldAttribute)atbt).IntegerDigits, '0');
                Double numberValue = Convert.ToDouble(value) * Math.Pow(10, ((BookNumberFieldAttribute)atbt).DecimalDigits);
                Value = numberValue.ToString(numberFormat);
            }

            if (Value != null)
            {

                if (stringsToMount.ContainsKey(atbt.StartIndex))
                {
                    throw new Exception(string.Format("Mais de uma propriedade com StartIndex igual a {0}", atbt.StartIndex));
                }
                stringsToMount.Add(atbt.StartIndex, Value);
            }
        }
    }
}
